﻿#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

#endregion

namespace Weazel.Badger.Vhdl.Types
{
  /// <summary>
  /// This class provides methods thay may be used to determine if a conversions
  /// exist between types.
  /// </summary>
  public static class TypeConverter
  {
		/// <summary>
		/// This method acts as a two dimensional table. The table is indexed by two types. 
		/// The method returns a list of types that each represents a conversion the first 
		/// of the index types should undergo in order to be converted into the second
		/// argument type.
		/// </summary>
		/// <param name="from"></param>
		/// <param name="to"></param>
		/// <param name="fromGezelType"></param>
		/// <returns></returns>
    private static Type[] getSimpleConversions(Type from, Type to, GezelType fromGezelType)
    {
      if (from.TypeId == to.TypeId)
        return new Type[] { from };

      if (from.TypeId == Types.StdLogicVector)
      {
        if (to.TypeId == Types.Integer)
          return new Type[] { from, new Unsigned(from.Width), to };

        if (to.TypeId == Types.Boolean)
          return null;

        if (to.TypeId == Types.StdLogic)
          return null;

        if (to.TypeId == Types.Character)

          return null;

        if (to.TypeId == Types.StdLogic)
          return null;

        if (to.TypeId == Types.Signed)
        {
          if (fromGezelType.Signed)
            return new Type[] { from, to };
          else
            return null;
        }

        if (to.TypeId == Types.Unsigned)
        {
          if (!fromGezelType.Signed)
            return new Type[] { from, to };
          else
            return null;
        }
      }

      if (from.TypeId == Types.Unsigned)
      {
        if (to.TypeId == Types.Boolean)
          return null;

        if (to.TypeId == Types.Character)
          return null;

        if (to.TypeId == Types.StdLogic)
          return null;

        if (to.TypeId == Types.Integer)
          return null;

        if (to.TypeId == Types.Signed)
          return null;
      }

      if (from.TypeId == Types.Signed)
      {
        if (to.TypeId == Types.Boolean)
          return null;

        if (to.TypeId == Types.Character)
          return null;

        if (to.TypeId == Types.StdLogic)
          return null;

        if (to.TypeId == Types.Integer)
          return null;

        if (to.TypeId == Types.Unsigned)
          return null;
      }

      if (from.TypeId == Types.Character)
      {
        if (to.TypeId == Types.StdLogic)
        {
          return new Type[] { from }; // conversion is implicit (for 0 and 1 ...)
        }

        if (to.TypeId == Types.StdLogicVector)
          return null;

        if (to.TypeId == Types.Unsigned)
          return null;

        if (to.TypeId == Types.Signed)
          return null;
      }

      if (from.TypeId == Types.Integer)
      {
        if (to.TypeId == Types.Integer)
        {
          return new Type[] { from };
        }

        if (to.TypeId == Types.StdLogic)
          return null;

        if (to.TypeId == Types.Boolean)
          return null;

        if (to.TypeId == Types.Character)
          return null;

        if (to.TypeId == Types.Unsigned)
          return new Type[] { from, new StdLogicVector(((Unsigned)to).Width), to };

        if (to.TypeId == Types.Signed)
          return new Type[] { from, new StdLogicVector(((Signed)to).Width), to };
      }

      if (from.TypeId == Types.Character)
      {
        if (to.TypeId == Types.Integer)
          return null;
        if (to.TypeId == Types.Boolean)
          return null;
      }

      if (from.TypeId == Types.StdLogic)
      {
        if (to.TypeId == Types.StdLogic)
          return new Type[] { from };

        if (to.TypeId == Types.Integer)
          return null;

        if (to.TypeId == Types.Boolean)
          return null;

        if (to.TypeId == Types.Unsigned)
          return new Type[] { from, to };

        if (to.TypeId == Types.Signed)
          return new Type[] { from, to };

        if (to.TypeId == Types.StdLogicVector)
        {
          if (fromGezelType == null)
            return null;
          else
          {
            return fromGezelType.Signed ?
                new Type[] { from, new Signed(to.Width), to }
              : new Type[] { from, new Unsigned(to.Width), to };
          }
        }       

        if (to.TypeId == Types.Character)
          return null; 
      }

      if (from.TypeId == Types.Boolean)
      {
        if (to.TypeId == Types.StdLogic)
          return null;

        if (to.TypeId == Types.StdLogicVector)
          return null;

        if (to.TypeId == Types.Signed)
          return null;

        if (to.TypeId == Types.Unsigned)
          return null;
      }

			// if control reaches this point it was not possible to convert 
			string message =
				string.Format("conversion '{0}' to '{1}' not supported.", from, to);

      throw new NotSupportedException(message);
    }

    private static Type[] getArrayConversions(Type from, GezelType fromGezelType, ArrayType to)
    {
      int width = to.Width;

      if (from == Types.StdLogicVector)
      {
        if (to == Types.StdLogicVector)
          return new Type[] { from };
        else if (to == Types.Signed)
        {
          if (fromGezelType != null && fromGezelType.Signed)
            return new Type[] { from, new Signed(width) };
          else
            return null;
        }
        else if (to == Types.Unsigned)
        {
          if (fromGezelType != null && !fromGezelType.Signed)
            return new Type[] { from, new Unsigned(width) };
          else
            return null;
        }
      }
      else if (from == Types.Unsigned)
      {
        if (to == Types.StdLogicVector)
          return new Type[] { from, new StdLogicVector(width) };
        else if (to == Types.Signed)
          return new Type[] { from, new Signed(width) };
        else if (to == Types.Unsigned)
          return new Type[] { from };
      }
      else if (from == Types.Signed)
      {
        if (to == Types.StdLogicVector)
          return new Type[] { from, new StdLogicVector(width) };
        else if (to == Types.Signed)
          return new Type[] { from };
        else if (to == Types.Unsigned)
          return new Type[] { from, new Unsigned(width) };
      }
      else if (from == Types.Integer)
      {
        if (to == Types.StdLogicVector)
          return new Type[] { from, new StdLogicVector(width) };
        else if (to == Types.Signed)
          return new Type[] { from, new StdLogicVector(width), new Signed(width) };
        else if (to == Types.Unsigned)
          return new Type[] { from, new StdLogicVector(width), new Unsigned(width) };
      }

      // not there?
      return null;
    }

    private static Type[] getArrayConversionsWithWidthShift(
      ArrayType from, 
      GezelType fromGezelType, 
      ArrayType to, 
      GezelType toGezelType      
    )
    {
      int sourceWidth = from.Width;
      int targetWidth = to.Width;

      if (from == Types.StdLogicVector)  
      {
        if (to == Types.StdLogicVector)
          return fromGezelType.Signed ?
            new Type[] { from, new Signed(sourceWidth), new StdLogicVector(targetWidth) } :
            new Type[] { from, new Unsigned(sourceWidth), new StdLogicVector(targetWidth) };
        else if (to == Types.Signed)
        {
					if (fromGezelType == null)
					{
						return null;
					}
					else return fromGezelType.Signed ?
						new Type[] { 
							from, 
							new Signed(sourceWidth), 
							new StdLogicVector(targetWidth), 
							new Signed(targetWidth) 
						} :
						new Type[] { 
							from, 
							new Unsigned(sourceWidth), 
							new StdLogicVector(targetWidth), 
							new Signed(targetWidth) 
						};
        }
        else if (to == Types.Unsigned)
          if (fromGezelType == null)
            return null;
          else return fromGezelType.Signed ?
            new Type[] { from, new Signed(sourceWidth), new StdLogicVector(targetWidth), new Unsigned(targetWidth) } :
            new Type[] { from, new Unsigned(sourceWidth), new StdLogicVector(targetWidth), new Unsigned(targetWidth) };
        else if (to == Types.Integer)
        {
          // Integer has no 'width' associated with it .. therefor:
          throw new NotSupportedException("Integer target is invalid");
        }
      }
      else if (from == Types.Unsigned)
      {
        if (to == Types.StdLogicVector)
          return new Type[] { from, new StdLogicVector(targetWidth) };
        else if (to == Types.Signed)
          return new Type[] { from, new StdLogicVector(targetWidth), new Signed(targetWidth) };
        else if (to == Types.Unsigned)
          return new Type[] { from, new StdLogicVector(targetWidth), new Unsigned(targetWidth) };
        else if (to == Types.Integer)
        {
          // Integer has no 'width' associated with it .. therefor:
          throw new NotSupportedException("Integer target is invalid");
        }
      }
      else if (from == Types.Signed)
      {
        if (to == Types.StdLogicVector)
          return new Type[] { from, new StdLogicVector(targetWidth) };
        else if (to == Types.Signed)
          return new Type[] { from, new StdLogicVector(targetWidth), new Signed(targetWidth) };
        else if (to == Types.Unsigned)
          return new Type[] { from, new StdLogicVector(targetWidth), new Unsigned(targetWidth) };
        else if (to == Types.Integer)
        {
          // Integer has no 'width' associated with it .. therefor:
          throw new NotSupportedException("Integer target is invalid");
        }
      }
      else if (from == Types.Integer)
      {
        // Integer has no 'width' associated with it .. therefor:
        throw new NotSupportedException("Integer target is invalid");
      }

      // not there?
      return null;
    }

    public static TypeConversionResult GetConversion(
      Type from, 
      Type to, 
      GezelType fromGezelType, 
      GezelType toGezelType, 
      bool ignoreWidthDifference
    )
    {
      if (to is ArrayType && !ignoreWidthDifference)
      {
        if (from.TypeId == Types.Integer)
        {
          Type[] conversions = getArrayConversions(from, fromGezelType, (ArrayType)to);
          return conversions != null ? new TypeConversionResult(conversions, false) : null;
        }
        else if (from is ArrayType && ((ArrayType)from).Width == ((ArrayType)to).Width)
        {
          Type[] conversions = getArrayConversions(from, fromGezelType, (ArrayType)to);
          return conversions != null ? new TypeConversionResult(conversions, false) : null;
        }
        else if (from is ArrayType && ((ArrayType)from).Width != ((ArrayType)to).Width)
        {
          Type[] conversions = getArrayConversionsWithWidthShift((ArrayType)from, fromGezelType, (ArrayType)to, toGezelType);
          return conversions != null ? new TypeConversionResult(conversions, false) : null;
        }
        else if (from is StdLogic)
        {
          Type[] simple = getSimpleConversions(from, to, fromGezelType);

          if (simple != null)
            return new TypeConversionResult(simple, false);
          else
            return null;
        }
        return null;
      }
      else
      {
        Type[] simple = getSimpleConversions(from, to, fromGezelType);

        if (simple != null)
          return new TypeConversionResult(simple, false);
        else
          return null;
      }
    }

		/// <summary>
		/// Get all necessary conversions between two types as
		/// a 'TypeConversionResult' object
		/// </summary>
		/// <param name="from">Type to be converted</param>
		/// <param name="to">Type to convert to</param>
		/// <param name="fromGezelType">Gezel type of "from"</param>
		/// <param name="toGezelType">Gezel type of "to"</param>
		/// <param name="ignoreWidthDifferences">Should differences in width be ignored?</param>
		/// <returns>Necessary conversions packed as an 'TypeConversionResult' object or null
		/// if the conversion was not possible</returns>
    public static TypeConversionResult GetConversion(
      TypeSet from, 
      Type to, 
      GezelType fromGezelType,
      GezelType toGezelType,     
      bool ignoreWidthDifferences
    )
    {
      System.Diagnostics.Debug.Assert(from.Count > 0);

      TypeConversionResult best = null;

      for (int i = 0; i < from.Count; i++)
      {
        TypeConversionResult result = 
          GetConversion(
            from[i], 
            to, 
            fromGezelType,
            toGezelType,        
            ignoreWidthDifferences
          );

        if (best == null)
          best = result;
        else if (result != null && result.Depth < best.Depth)
          best = result;
      }

      return best;
    }
  }
}
